package com.px.user.service;

import com.google.common.collect.ImmutableMap;
import com.px.common.core.RespCode;
import com.px.common.core.Response;
import com.px.common.util.JsonUtil;
import com.px.user.entity.ChargeMoney;
import com.px.user.entity.ConstT;
import com.px.user.entity.User;
import com.px.user.entity.Withdraw;
import com.px.user.mapper.ChargeMoneyMapper;
import com.px.user.mapper.ConstTMapper;
import com.px.user.mapper.UserMapper;
import com.px.user.mapper.WithdrawMapper;
import com.px.user.model.MoneyStatus;
import com.px.user.model.PayReqModel;
import com.px.user.model.PayRespModel;
import com.px.user.model.WithdrawSum;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.digest.DigestUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.http.HttpEntity;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.client.RestTemplate;

import java.lang.reflect.Field;
import java.math.BigDecimal;
import java.sql.Timestamp;
import java.util.Objects;
import java.util.TreeMap;
import java.util.concurrent.TimeUnit;

@Slf4j
@Service
public class WithdrawService {

    @Autowired
    private ChargeMoneyMapper chargeMoneyMapper;

    @Autowired
    private WithdrawMapper withdrawMapper;

    @Autowired
    private UserMapper userMapper;

    @Autowired
    private UserService userService;

    private final String DAY_WITHDRAW_COUNT = "DAY-WITHDRAW-COUNT";
    private final String DAY_WITHDRAW_MONEY = "DAY-WITHDRAW-MONEY";

    @Value("${px.notify.charge-result}")
    private String chargeResult;

    @Autowired
    private ConstTMapper constTMapper;

    @Autowired
    private RedisTemplate<String, String> redisTemplate;

    @Value("${px.pay.url}")
    private String PAY_URL;
    @Value("${px.pay.MerchantId}")
    private String PAY_MerchantId;
    @Value("${px.pay.KEY}")
    private String PAY_KEY;
    @Value("${px.pay.CallBackUrl}")
    private String PAY_CallBackUrl;

    private final String PAY_SIGNATURE = "Signature";

    @Transactional
    public void chargeManual(ChargeMoney chargeMoney) {
        chargeMoney.setStatus(MoneyStatus.SUCCESS.getStatus());
        chargeMoney.setChannel("人工");
        chargeMoneyMapper.insert(chargeMoney);
        userMapper.addMoney(User.builder().id(chargeMoney.getUserId()).money(chargeMoney.getMoney()).build());
    }

    public PayRespModel.InnerData charge(ChargeMoney chargeMoney) throws IllegalAccessException {
        chargeMoney.setStatus(MoneyStatus.CHARGING.getStatus());
        chargeMoneyMapper.insert(chargeMoney);

        PayReqModel payReqModel = PayReqModel.builder()
                .MerchantId(PAY_MerchantId)
                .PaymentChannelNo(chargeMoney.channelNo)
                .PaymentType(chargeMoney.paymentType)
                .OrderNo(String.valueOf(chargeMoney.getId()))
                .Amount(String.format("%.2f", chargeMoney.getMoney().doubleValue()))
                .ReturnUrl("")
                .CallBackUrl(PAY_CallBackUrl)
                .Description("")
                .build();
        String sig = md5Signature(payReqModel);
        payReqModel.setSignature(sig);

        HttpEntity<PayReqModel> req = new HttpEntity<>(payReqModel);
        PayRespModel payRespModel = new RestTemplate().postForObject(PAY_URL, req, PayRespModel.class);
        if (payRespModel == null) {
            log.error(String.format("charge url: %s request: %s without response", PAY_URL, payReqModel.toString()));
            return null;
        }
        if (payRespModel.getCode().equals("200"))
            return payRespModel.getData();
        else {
            log.error(String.format(
                    "charge url: %s request: %s response: %s", PAY_URL, payReqModel.toString(), payRespModel.toString()));
            return null;
        }
    }

    public String md5Signature(Object object) throws IllegalAccessException {
        TreeMap<String, String> sortArg = new TreeMap<>();
        Field[] fields = object.getClass().getDeclaredFields();
        for (Field field : fields) {
            if (field.getName().equals(PAY_SIGNATURE))
                continue;
            field.setAccessible(true);
            if (field.getType() == Double.class)
                sortArg.put(field.getName().toUpperCase(),
                        String.format("%.2f", Double.parseDouble(String.valueOf(field.get(object)))));
            else
                sortArg.put(field.getName().toUpperCase(), String.valueOf(field.get(object)));
        }
        StringBuilder sigArg = new StringBuilder();
        sortArg.forEach((k, v) -> sigArg.append(k).append("=").append(v).append("&"));
        sigArg.append("KEY=").append(PAY_KEY);
        return DigestUtils.md5Hex(sigArg.toString());
    }

    @Transactional
    public boolean chargeResult(ChargeMoney chargeMoney) {
        if (chargeMoney.getStatus().equals(MoneyStatus.SUCCESS.getStatus())) {
            ChargeMoney fChargeMoney = chargeMoneyMapper.findById(chargeMoney.getId());
            if (fChargeMoney == null)
                return false;
            if (fChargeMoney.getStatus().equals(MoneyStatus.CHARGING.getStatus()) && fChargeMoney.getMoney().compareTo(chargeMoney.getMoney()) == 0) {
                userService.addMoney(fChargeMoney.getUserId(), fChargeMoney.getMoney());
                chargeMoneyMapper.updateStatus(chargeMoney);

                User user = userMapper.findById(fChargeMoney.getUserId());
                Long roomId = user.getInviteRoomId();
                redisTemplate.opsForList().leftPush(chargeResult, Objects.requireNonNull(
                        JsonUtil.toJson(ImmutableMap.of(
                                "userId", user.getId(),
                                "chargeMoney", chargeMoney.getMoney(),
                                "totalMoney", user.getMoney(),
                                "roomId", roomId == null ? -1 : roomId))));
                return true;
            }
        }
        return true;
    }

    public Response withdraw(Withdraw withdraw) {
        User user = userMapper.findById(withdraw.getUserId());
        if (user.getIsRobot() != null && user.getIsRobot() == 1)
            return Response.fail(RespCode.FORBIDDEN, "机器人账号禁止提现");
        if (user.getIsVirtual() != null && user.getIsVirtual() == 1)
            return Response.fail(RespCode.FORBIDDEN, "虚拟账号禁止提现");
        if (user.getMoney().compareTo(withdraw.getMoney()) < 0)
            return Response.fail(RespCode.NOT_ENOUGH_MONEY, "您仅有: " + user.getMoney());

        WithdrawSum withdrawSum =
                withdrawMapper.sumByUserId(Withdraw.builder()
                        .userId(withdraw.getUserId())
                        .startTime(new Timestamp(System.currentTimeMillis() - TimeUnit.DAYS.toMillis(1)))
                        .endTime(new Timestamp(System.currentTimeMillis()))
                        .build());
        ConstT cMoney = constTMapper.findByCode(DAY_WITHDRAW_MONEY);
        double money = cMoney.getValue() == null ? 10000 : Double.parseDouble(cMoney.getValue());
        ConstT cCount = constTMapper.findByCode(DAY_WITHDRAW_COUNT);
        int count = cCount.getValue() == null ? 10 : Integer.parseInt(cCount.getValue());
        if (withdrawSum.getTotal().add(withdraw.getMoney()).compareTo(user.getMoney()) > 0)
            return Response.fail(RespCode.NOT_ENOUGH_MONEY, "累计提现申请金额超过总金额: " + user.getMoney());
        if (withdrawSum.getTotal().add(withdraw.getMoney()).compareTo(BigDecimal.valueOf(money)) > 0)
            return Response.fail(RespCode.WITHDRAW_OVER, "累计提现申请金额超过: " + money);
        if ((withdrawSum.getCount() + 1) > count)
            return Response.fail(RespCode.WITHDRAW_OVER, "累计提现申请次数超过: " + count);

        userMapper.update(User.builder()
                .id(user.getId())
                .name(withdraw.getName())
                .bankCard(withdraw.getBankCard())
                .bankName(withdraw.getBankName())
                .build());

        withdraw.setStatus(MoneyStatus.AUDITING.getStatus());
        withdrawMapper.insert(withdraw);
        return Response.success(null);
    }

    @Transactional
    public void withdrawUpdate(Withdraw withdraw) {
        Withdraw fWithdraw = withdrawMapper.findById(withdraw.getId());
        if (fWithdraw == null)
            return;
        if (!fWithdraw.getStatus().equals(MoneyStatus.SUCCESS.getStatus()) &&
                withdraw.getStatus().equals(MoneyStatus.SUCCESS.getStatus()))
            userMapper.addMoney(User.builder().id(fWithdraw.getUserId()).money(fWithdraw.getMoney().negate()).build());

        userMapper.update(User.builder()
                .name(withdraw.getName())
                .bankCard(withdraw.getBankCard())
                .bankName(withdraw.getBankName())
                .build());
        withdrawMapper.update(withdraw);
    }
}
